#include <math.h>
#include <cassert>
#include <iostream>

#include "qgame.h"
#include "qworldscene.h"
#include "qworld.h"
#include "qanimal.h"
#include "qgravity.h"
#include "qstatistic.h"
#include "qlife.h"
#include "dna.h"
#include "qgravitygraphicsitem.h"
#include "qanimalgraphicsitem.h"
#include "qstatisticgraph.h"


QGame::QGame ( QWorld* parent, int width, int height ) : QObject ( ( QObject* ) parent )
{
	world = parent;

	this -> width = width;
	this -> height = height;

	scene = NULL;
	dnaStat = NULL;
	lifeStat = NULL;

	started = false;

	writeLifeLogFile = false;
	writeDnaLogFile = false;

	reset();

	msec = 50;
	sceneRatio = 1;
	statRatio = 1;

	timer = new QTimer ( this );
	connect ( timer, SIGNAL ( timeout() ), this, SLOT ( step() ) );
}

void QGame::addQAnimalProto ( QColor color )
{
	addQAnimal ( new Dna ( 50, color ) );
}

void QGame::addQGravityProto ( QColor color )
{
	addQGravity ( new Dna ( 0, color ) );
}

void QGame::setDimension ( int width, int height )
{
	if ( !started )
	{
		this -> width = width;
		this -> height = height;
	}
}

void QGame::setScene ( QWorldScene* scene )
{
	this -> scene = scene;
}

void QGame::setLifeStatistic ( QStatistic* stat )
{
	this -> lifeStat = stat;

	gravityPopSizeID = lifeStat -> addGraph ( new QStatisticGraph ( QColor ( 64, 200, 64 ), "Gravity Population Size: %1" ) );
	animalPopSizeID = lifeStat -> addGraph ( new QStatisticGraph ( QColor ( 200, 64, 64 ), "Animal Population Size: %1" ) );
	avrgAnimalNrjID = lifeStat -> addGraph ( new QStatisticGraph ( QColor ( 200, 200, 64 ), "Avrg Animal Energy: %1" ) );
	avrgAnimalAgeID = lifeStat -> addGraph ( new QStatisticGraph ( QColor ( 200, 64, 200 ), "Avrg Animal Age: %1" ) );
	avrgGravityNrjID = lifeStat -> addGraph ( new QStatisticGraph ( QColor ( 64, 200, 200 ), "Avrg Gravity Energy: %1" ) );
}

void QGame::setDnaStatistic ( QStatistic* stat )
{
	this -> dnaStat = stat;

	avrgMaxSpeedID = dnaStat -> addGraph ( new QStatisticGraph ( QColor ( 64, 200, 64 ), "Avrg Max Speed (x10): %1" ) );
	avrgMaxAgeID = dnaStat -> addGraph ( new QStatisticGraph ( QColor ( 200, 64, 64 ), "Avrg Max Age: %1" ) );
	avrgViewDistanceID = dnaStat -> addGraph ( new QStatisticGraph ( QColor ( 64, 64, 200 ), "Avrg View Distance: %1" ) );
	avrgAttackStrengthID = dnaStat -> addGraph ( new QStatisticGraph ( QColor ( 200, 200, 64 ), "Avrg Attack Strength (x10): %1" ) );
	avrgWaitWillingnessID = dnaStat -> addGraph ( new QStatisticGraph ( QColor ( 0, 0, 0 ), "Avrg Wait Willingness: %1" ) );
}

void QGame::logLifeStatistics ( bool log )
{
	if ( log && !writeLifeLogFile )
	{
		lifeFilePointer = fopen ( "life.log", "w" );
		fprintf ( lifeFilePointer,
		          "#\tTime\tAnimal Energy\tAnimal Age\tGravity Energy\tAnimal Population Size\tGravity Population Size\n" );
		writeLifeLogFile = true;
	}
	else if ( !log && writeLifeLogFile )
	{
		fclose ( lifeFilePointer );
		writeLifeLogFile = false;
	}
}

void QGame::logDnaStatistics ( bool log )
{
	if ( log && !writeDnaLogFile )
	{
		dnaFilePointer = fopen ( "dna.log", "w" );
		fprintf ( dnaFilePointer,
		          "#\tTime\tAvrg Max Age\tAvrg Max Speed\tAvrg View Distance\tAvrg Attack Strength\tAvrg Wait Willingness\n" );
		writeDnaLogFile = true;
	}
	else if ( !log && writeDnaLogFile )
	{
		fclose ( dnaFilePointer );
		writeDnaLogFile = false;
	}
}

void QGame::step()
{
	// life statistics
	float avrgAnimal = 0;
	float avrgAge = 0;
	float avrgGravity = 0;

	// dna statistics
	float avrgMaxSpeed = 0;
	float avrgViewDistance = 0;
	float avrgAttackStrength = 0;
	float avrgMaxAge = 0;
	float avrgWaitWillingness = 0;

	QGravity* currentGravity;
	QAnimal* currentAnimal;

	// DRAW TO MAIN CANVAS
	for ( int i = 0; i < animalList.size(); i++ )
	{
		currentAnimal = animalList.at ( i );

		if ( started )
		{
			currentAnimal -> update();

			if ( currentAnimal -> isFinished() )
			{
				scene -> removeLife ( currentAnimal -> getId() );
				animalList.removeAt ( i );
				animalPopulationSize--;

				continue;
			}

			avrgAnimal += currentAnimal -> getEnergy();
			avrgAge += currentAnimal -> getAge();

			avrgMaxAge += ( currentAnimal -> getDna() ) -> getMaxAge();
			avrgMaxSpeed += ( currentAnimal -> getDna() ) -> getMaxSpeed();
			avrgViewDistance += ( currentAnimal -> getDna() ) -> getViewDistance();
			avrgAttackStrength += ( currentAnimal -> getDna() ) -> getAttackStrength();
			avrgWaitWillingness += (currentAnimal -> getDna() ) -> getWaitWillingness();
		}
	}

	for ( int i = 0; i < gravityList.size(); i++ )
	{
		currentGravity = gravityList.at ( i );

		if ( started )
		{
			currentGravity -> update();

			if ( currentGravity -> isFinished() )
			{
				scene -> removeLife ( currentGravity -> getId() );
				gravityList.removeAt ( i );
				gravityPopulationSize--;

				continue;
			}

			avrgGravity += currentGravity -> getEnergy();
		}
	}

	// only draw to canvas at given ratio
	if ( ( globalTime % sceneRatio ) == 0 ) scene -> advance();

	// STATISTICS
	if ( started && ( ( globalTime % statRatio ) == 0 ) )
	{
		// reset time
		if ( lifeStatTime > ( lifeStat -> sceneRect().width() ) ) lifeStatTime = 0;
		if ( dnaStatTime > ( dnaStat -> sceneRect().width() ) ) dnaStatTime = 0;

		// calculate average for animal
		if ( animalList.size() != 0 )
		{
			// calc the average
			avrgAnimal = avrgAnimal / animalList.size();
			avrgAge = avrgAge / animalList.size();
			avrgMaxAge = avrgMaxAge / animalList.size();
			avrgMaxSpeed = avrgMaxSpeed / animalList.size();
			avrgViewDistance = avrgViewDistance / animalList.size();
			avrgAttackStrength = avrgAttackStrength / animalList.size();
			avrgWaitWillingness = avrgWaitWillingness / animalList.size();
		}

		// calculate average for gravity
		if ( gravityList.size() != 0 )
		{
			// calc the average
			avrgGravity = avrgGravity / gravityList.size();
		}

		if ( writeLifeLogFile )
		{
			fprintf ( lifeFilePointer,
			          "\t%d\t%f\t%f\t%f\t%d\t%d\n",
			          globalTime, avrgAnimal, avrgAge, avrgGravity, animalPopulationSize, gravityPopulationSize );
		}

		if ( writeDnaLogFile )
		{
			fprintf ( dnaFilePointer,
			          "\t%d\t%f\t%f\t%f\t%f\t%f\n",
			          globalTime, avrgMaxAge, avrgMaxSpeed, avrgViewDistance, avrgAttackStrength, avrgWaitWillingness );
		}

		// add to graph
		lifeStat -> pushPoint ( gravityPopSizeID, QPointF ( lifeStatTime, -gravityPopulationSize ) );
		lifeStat -> pushPoint ( animalPopSizeID, QPointF ( lifeStatTime, -animalPopulationSize ) );
		lifeStat -> pushPoint ( avrgAnimalAgeID, QPointF ( lifeStatTime, -avrgAge ) );
		lifeStat -> pushPoint ( avrgAnimalNrjID, QPointF ( lifeStatTime, -avrgAnimal ) );
		lifeStat -> pushPoint ( avrgGravityNrjID, QPointF ( lifeStatTime, -avrgGravity ) );

		dnaStat -> pushPoint ( avrgMaxSpeedID, QPointF ( dnaStatTime, -10 * avrgMaxSpeed ) );
		dnaStat -> pushPoint ( avrgViewDistanceID, QPointF ( dnaStatTime, -avrgViewDistance ) );
		dnaStat -> pushPoint ( avrgAttackStrengthID, QPointF ( dnaStatTime, -10 * avrgAttackStrength ) );
		dnaStat -> pushPoint ( avrgMaxAgeID, QPointF ( dnaStatTime, -avrgMaxAge ) );
		dnaStat -> pushPoint ( avrgWaitWillingnessID, QPointF ( dnaStatTime, -avrgWaitWillingness ) );

		lifeStat -> stepAdvance();
		dnaStat -> stepAdvance();

		lifeStatTime++;
		dnaStatTime++;
	}

	globalTime++;
}

void QGame::changeLifeUpdateInterval ( int msec )
{
	this -> msec = msec;
	if ( started ) timer -> setInterval ( msec );
}

void QGame::changeSceneUpdateRatio ( int ratio )
{
	this -> sceneRatio = ratio;
}

void QGame::changeStatUpdateRatio ( int ratio )
{
	this -> statRatio = ratio;
}

int QGame::getAnimalPopulationSize()
{
	return animalPopulationSize;
}

int QGame::getGravityPopulationSize()
{
	return gravityPopulationSize;
}

void QGame::addQAnimal ( Dna* dna )
{
	QAnimal* animal = new QAnimal ( this, dna, id, width, height );
	connect ( animal, SIGNAL ( birth ( Dna* ) ), this,
	          SLOT ( addQAnimal ( Dna* ) ) );
	connect ( animal, SIGNAL ( config ( long, QStringList ) ), ( QObject* ) world -> getLifeTable(),
	          SLOT ( receiveLife ( long, QStringList ) ) );

	animalList.append ( animal );
	animalPopulationSize++;

	id++;

	scene -> addLife ( animal, new QAnimalGraphicsItem ( animal, dna -> getColor() ) );

	if ( !started ) step();

	if ( started ) animal -> start();
}

void QGame::addQGravity ( Dna* dna )
{
	QGravity* grv = new QGravity ( this, dna, id, width, height );
	connect ( grv, SIGNAL ( birth ( Dna* ) ), this,
	          SLOT ( addQGravity ( Dna* ) ) );
	connect ( grv, SIGNAL ( config ( long, QStringList ) ), ( QObject* ) world -> getLifeTable(),
	          SLOT ( receiveLife ( long, QStringList ) ) );

	gravityList.append ( grv );
	gravityPopulationSize++;

	id++;

	scene -> addLife ( grv, new QGravityGraphicsItem ( grv, dna -> getColor() ) );

	if ( !started ) step();

	if ( started ) grv -> start();
}

QList<QAnimal*> QGame::getQAnimals()
{
	return animalList;
}

QList<QGravity*> QGame::getQGravities()
{
	return gravityList;
}

void QGame::start()
{
	assert ( started == false );

	QAnimal* currentAnimal;
	QGravity* currentGravity;

	for ( int i = 0; i < gravityList.size(); ++i )
	{
		currentGravity = gravityList.at ( i );
		currentGravity -> start();
	}

	for ( int i = 0; i < animalList.size(); ++i )
	{
		currentAnimal = animalList.at ( i );
		currentAnimal -> start();
	}

	timer -> start ( msec );

	started = true;
}

void QGame::pause()
{
	if ( started == false ) return;

	assert ( timer -> isActive() );

	QAnimal* currentAnimal;
	QGravity* currentGravity;

	timer -> stop();

	started = false;

	assert ( !timer -> isActive() );

	for ( int i = 0; i < animalList.size(); ++i )
	{
		currentAnimal = animalList.at ( i );
		currentAnimal -> quit();
	}

	for ( int i = 0; i < gravityList.size(); ++i )
	{
		currentGravity = gravityList.at ( i );
		currentGravity -> quit();
	}
}

void QGame::reset()
{
	if ( started ) pause();

	assert ( started == false );

	animalList.clear();
	gravityList.clear();

	if ( scene != NULL ) scene -> reset();
	if ( lifeStat != NULL ) lifeStat -> reset();
	if ( dnaStat != NULL ) dnaStat -> reset();

	animalPopulationSize = 0;
	gravityPopulationSize = 0;

	lifeStatTime = 0;
	dnaStatTime = 0;
	globalTime = 0;

	id = 0;
}

QGravity* QGame::getNextGravity ( QAnimal* animal )
{
	return ( QGravity* ) getNextQLife ( animal, animal -> getViewDistance(), gravityList );
}

QAnimal* QGame::getNextAnimal ( QAnimal* animal )
{
	return ( QAnimal* ) getNextQLife ( animal, animal -> getViewDistance(), animalList );
}

QLife* QGame::getIntersectingLife ( QLife* life, QString searchForName, float viewDistance )
{
	QLife* currentLife;
	QGraphicsItem* currentItem;
	QList<QGraphicsItem*> items = scene -> getIntersectingItems ( life -> getId(), viewDistance );

	if ( items.size() == 0 ) return NULL;

	for ( int i = 0; i < items.size(); ++i )
	{
		currentItem = items.at ( i );

		if ( currentItem -> data ( 0 ).toString() != searchForName )
		{
			continue;
		}
		else
		{
			currentLife = ( ( QLifeGraphicsItem* ) currentItem ) -> getLife();

			if ( ! ( currentLife -> getId() == life -> getId() ) ) return currentLife;
		}
	}

	return NULL;
}

template <typename T>
QLife* QGame::getNextQLife ( QLife* life, float searchRadius, QList<T*> lifeList )
{
	QLife* currentLife = NULL;
	QLife* bestLife = NULL;

	float ax = life -> getX();
	float ay = life -> getY();
	float ex;
	float ey;

	float d = 0;
	float bestDistance = searchRadius;

	for ( int i = 0; i < lifeList.size(); ++i )
	{
		currentLife = lifeList.at ( i );

		if ( currentLife == life ) continue;

		ex = currentLife -> getX();
		ey = currentLife -> getY();

		d = sqrt ( pow ( ax - ex, 2 ) + pow ( ay - ey, 2 ) );

		if ( ( d < bestDistance ) && ( d < searchRadius ) )
		{
			bestLife = currentLife;
			bestDistance = d;
		}
	}

	return bestLife;
}
